/*
 * Copyright (c) 2016-2021, Hangzhou Qulian Technology Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "services.h"
#include <stdlib.h>
/////////////eth///////////////
#define Bytes0_55Len      (0x80)
#define Bytes56_Len       (0xb7)
#define Lists0_55Len      (0xc0)
#define Lists56_Len       (0xf7)

typedef enum RLP_TYPE{
    RLP_BYTES,
    RLP_LISTS,
}RLP_TYPE;

// response
void BasicRspInit(basic_response_t * const basicrsp) {
    basicrsp->jsonrpc = NULL;
    basicrsp->id = 0;
    basicrsp->code = -1;
    basicrsp->message = NULL;
    basicrsp->nameSpace = NULL;
}

void CleanBasicrsp(basic_response_t * basicrsp) {
    free(basicrsp->jsonrpc);
    free(basicrsp->message);
    free(basicrsp->nameSpace);
    BasicRspInit(basicrsp);
}

response_err_t BasicRspFromString(basic_response_t *const basicrsp, cJSON **result, ql_byte_t* str) {
    if(basicrsp == NULL || str == NULL)return RESPONSE_ERR_PARAM_INVALID;
    //get value from json
    cJSON *items = cJSON_Parse((char*)str);
    if(items == NULL) {
        log_debug("parse json string fail\n");
        return RESPONSE_ERR_PARAM_INVALID;
    }
    *result = cJSON_GetObjectItem(items, "result");
    *result = cJSON_Duplicate(*result, 1);
    cJSON *jsonrpc = cJSON_GetObjectItem(items, "jsonrpc");
    cJSON *id = cJSON_GetObjectItem(items, "id");
    cJSON *code = cJSON_GetObjectItem(items, "code");
    cJSON *message = cJSON_GetObjectItem(items, "message");
    cJSON *nameSpace = cJSON_GetObjectItem(items, "namespace");
    //jsonrpc string
    if(jsonrpc == NULL || jsonrpc->valuestring == NULL) {
        return RESPONSE_ERR_PARAM_INVALID;
    } else {
        basicrsp->jsonrpc = StrMalloc(jsonrpc->valuestring, strlen(jsonrpc->valuestring));
    }
    //id numeric
    if(id != NULL) {
        basicrsp->id = id->valueint;
    }
    //code numeric
    if(code == NULL) {
        return RESPONSE_ERR_PARAM_INVALID;
    } else {
        basicrsp->code = code->valueint;
    }
    //message string
    if(message == NULL || message->valuestring == NULL) {
        return RESPONSE_ERR_PARAM_INVALID;
    } else {
        basicrsp->message = StrMalloc(message->valuestring, strlen(message->valuestring));
    }
    //namespace string
    if(nameSpace != NULL && nameSpace->valuestring != NULL) {
        basicrsp->nameSpace = StrMalloc(nameSpace->valuestring, strlen(nameSpace->valuestring));
    }
    cJSON_Delete(items);
    return RESPONSE_SUCCESS;
}

char* getReceipt(char* url, char* txhash) {
    if(txhash == NULL)return NULL;
    char* ret = NULL;
    char* tmp = "{\"jsonrpc\":\"2.0\",\"namespace\":\"global\",\"method\":\"tx_getTransactionReceipt\",\"params\":[\"%s\"],\"id\":1}";
    char send_buf[1024];
    ql_strings_t *send_body = CreateStringsT();
    ql_strings_t *recv;
    if(send_body == NULL)return NULL;
    sprintf(send_buf, tmp, txhash);
    send_body->val = StrMalloc(send_buf, strlen(send_buf));
    send_body->len = strlen(send_buf);
    sendToBlockchain(url, send_body, &recv);
    FreeStringsT(send_body);
    if(recv == NULL)return NULL;
    ret = StrMalloc(recv->val, recv->len);
    FreeStringsT(recv);
    return ret;
}

char *pollingReceipt(char* url, char *txHash) {
    int i = 0;
    basic_response_t brp;
    cJSON *result;
    char *tmp;
    unsigned char isGet = 0;
    for(i = 0; i < 15; i++) {
        //wait
        printf("waiting......\n");
        sleep(1);
        //init
        BasicRspInit(&brp);
        result = NULL;
        //get receipt
        tmp = getReceipt(url, txHash);
        if(tmp == NULL)continue;
        //parse receipt
        ql_err_t err = BasicRspFromString(&brp, &result, tmp);
        if(brp.code == 0)isGet = 1;
        //free mem
        CleanBasicrsp(&brp);
        cJSON_Delete(result);
        if(isGet == 0)free(tmp);
        //break or not
        if(isGet == 1)break;
    }
    if(isGet == 1)return tmp;
    return NULL;
}


// post
static size_t WRCallback(void* data, size_t size, size_t nmemb, void* userdata) {
    //size of data received
    size_t realsize = size * nmemb;
    //change type of my mem
    ql_strings_t* recv_data = (ql_strings_t*)userdata;
    //save old data
    char *ptr = realloc(recv_data->val, recv_data->len + realsize+1);
    if(ptr == NULL)return 0;
    recv_data->val= ptr;
    //save new data
    memcpy(&(recv_data->val[recv_data->len]), data, realsize);
    //new size
    recv_data->len += realsize;
    //end of string
    recv_data->val[recv_data->len] = 0;
    // printf("recv in wr_callback:%s\n", recv_data->val);
    return realsize;
}

void sendToBlockchain(ql_byte_t* url, ql_strings_t* post_Body, ql_strings_t **recvbuf) {
    *recvbuf = NULL;
    if(url == NULL)return;
    CURL *curl;
    CURLcode res;
    curl = curl_easy_init();
    struct curl_slist *list = NULL;
    if(curl) {
        list = curl_slist_append(list, "Content-Type: application/json");//for eth
        if(list == NULL) {
            printf("list error\n");
            return;
        }
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
        // char url[strlen(ip) + sizeof(port)*8];
        // sprintf(url, "%s:%d", ip, port);
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_Body->len);
        curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, post_Body->val);
        ql_strings_t buf;
        buf.len = 0;
        buf.val = NULL;
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WRCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
        res = curl_easy_perform(curl);
        if(res != CURLE_OK) {
            printf("curl err\n");
            *recvbuf = NULL;
        } else {
            *recvbuf = (ql_strings_t*)malloc(sizeof(ql_strings_t));
            (*recvbuf)->len = buf.len;
            (*recvbuf)->val = (ql_byte_t*)calloc(buf.len+1, sizeof(ql_byte_t));
            (*recvbuf)->next = NULL;
            memcpy((*recvbuf)->val, buf.val, buf.len);
            free(buf.val);
            buf.val = NULL;
            (*recvbuf)->val[(*recvbuf)->len] = 0;
        }
        curl_easy_cleanup(curl);
        curl_slist_free_all(list);
    }
}
#if defined(unix) || defined(__linux__) || defined(__unix__) || defined(__unix) || \
    (defined(__APPLE__) && defined(__MACH__)) || defined(uECC_POSIX)
#ifdef OpenHarmony_SYS
static long long stringToLong(char* str)
{
    int n = strlen(str);
    long long res = 0;
    int i;
    for(i = 0; i < n; i++)
        res = res*10 + str[i]-'0';
    return res;
}

static long long timelong;
void setCurrentTime(void) {
    CURL *curl;
    CURLcode res;
    timelong = -1;
    curl = curl_easy_init();
    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp");
        ql_strings_t buf;
        buf.len = 0;
        buf.val = NULL;
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WRCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
        res = curl_easy_perform(curl);
        if(res != CURLE_OK) {
            printf("curl err\n");
        } else {
            cJSON* items = cJSON_Parse((char*)buf.val);
            cJSON*  data = cJSON_GetObjectItem(items, "data");
            char* timestring = cJSON_GetObjectItem(data, "t")->valuestring;
            timelong = stringToLong(timestring);
            cJSON_Delete(items);
            free(buf.val);
            buf.val = NULL;
        }
        curl_easy_cleanup(curl);
    }
    printf("setCurretnTime:%lld\n", timelong);
    // return ret;
}
void setTimestamp(long long *dst) {
    struct timeval t;
    gettimeofday(&t,NULL);
    *dst = (long long)(t.tv_sec+timelong/1000) * 1000000000 + (long long)(t.tv_usec+timelong % 1000 * 1000)*1000; // ns
    
}
#else
void setTimestamp(long long *dst) {
    struct timespec cur_time = {0, 0}; 
    clock_gettime(CLOCK_REALTIME, &cur_time);
    *dst = (ql_int64_t)cur_time.tv_sec * 1000000000 + (ql_int64_t)cur_time.tv_nsec;
}
#endif
#endif

// eth
static ql_int64_t getHexStringSize(char *in) {
    if(in == NULL)return -1;
    ql_int64_t len = strlen(in);
    if(len < 0)return -1;
    if(len & (ql_int64_t)0x1)return -1;//len is odd
    if(len >= 2 && in[0] == '0' && in[1] == 'x')len -= 2;
    return len >> 1;//len/2
}


static void bytesToRlpbytes(ql_strings_t* const inout, RLP_TYPE type) {
    if(inout == NULL)return;
    ql_size_t prefix_len = 0;
    ql_size_t len_size = GetHexNumericSize(inout->len);
    ql_byte_t prefix[1 + len_size + 1];//new information 
    ql_size_t i = 0;
    ql_byte_t *new_val;//rlp result
    if(inout->len == 1 && type == RLP_BYTES && inout->val[0] <= 0x7f && inout->val[0] >= 0) {
        return;
    }else if(inout->len <= 55) {
        prefix_len = 1;
        prefix[0] = (type == RLP_BYTES ? Bytes0_55Len : Lists0_55Len) + inout->len;//0x80 + len(val)
    } else {
        prefix_len = 1 + len_size;
        prefix[0] = (type == RLP_BYTES ? Bytes56_Len : Lists56_Len) + len_size;//0x80 + len(len(val))
        ql_strings_t* tmp = NumericToHexbytes(inout->len);//len bytes
        for(i = 0; i < tmp->len; i++)prefix[i + 1] = tmp->val[i];
        FreeStringsT(tmp);
    }
    new_val = (ql_byte_t*)calloc(prefix_len + inout->len + 1, sizeof(ql_byte_t));
    for(i = 0; i < prefix_len; i++)new_val[i] = prefix[i];//add prefix
    memcpy(&new_val[i], inout->val, inout->len);
    free(inout->val);
    inout->val = new_val;
    inout->len += prefix_len;
}

static ql_strings_t* numericToRlpbytes(ql_uint64_t in) {
    ql_strings_t* ret;
    if(in) {
        ret = NumericToHexbytes(in);
    } else {
        ret = CreateStringsT();
        ret->val = StrMalloc("", 0);
    }
    bytesToRlpbytes(ret, RLP_BYTES);
    return ret;
}

//return a struct even no in
static ql_strings_t* hexstringToRlpbytes(char* in) {
    ql_strings_t *ret;
    ret = HexstringToBytes(in);
    if(ret == NULL) {
        ret = CreateStringsT();//create NULL
        ret->val = StrMalloc("", 0);
    }
    bytesToRlpbytes(ret, RLP_BYTES);
    return ret;
}

static ql_strings_t* connectStringsT(const ql_strings_t* const root) {
    ql_size_t newlen = 0;
    ql_strings_t *ptr = root;
    ql_strings_t *ret;
    // int i = 0;
    while(ptr){
        newlen += ptr->len; 
        ptr = ptr->next;
    }
    ret = CreateStringsT();
    if(ret == NULL)return NULL;
    ret->val = (ql_byte_t*)calloc(newlen + 1, sizeof(ql_byte_t));
    if(ret->val == NULL) {
        FreeStringsT(ret);
        return NULL;
    }
    ptr = root;
    ret->len = 0;
    // i = 0;
    while(ptr) {
        if(ptr->val)memcpy(&(ret->val[ret->len]), ptr->val, ptr->len);
        ret->len += ptr->len;
        ptr = ptr->next;
        
    }
    return ret;
}

static char* getRawtx(ql_int32_t nonce, ql_int64_t gasPrice, ql_int64_t gas, char* to, ql_int64_t value, char* payload, char *priv) {
    //1.get each fied's rlp
    if(to != NULL && strcmp(to, ADDR_ZERO) == 0)to = ADDR_ZERO_LONG;//eth differs from hpc
    ql_strings_t *rlp_nonce = numericToRlpbytes(nonce);
    ql_strings_t *rlp_gasPrice = numericToRlpbytes(gasPrice);
    ql_strings_t *rlp_gas = numericToRlpbytes(gas);
    ql_strings_t *rlp_to = hexstringToRlpbytes(to);
    ql_strings_t *rlp_value = numericToRlpbytes(value);
    ql_strings_t *rlp_payload = hexstringToRlpbytes(payload);
    ql_strings_t *prefix_bytes;
    ql_strings_t *raw_bytes;//raw_bytes = needhash_bytes + rsv
    char *rawtx;
    char data_hash[32];
    // char* needhash;
    ql_byte_t* hash_ptr = data_hash;
    ql_byte_t sig[65];//27+v r s
    ql_strings_t* r = NULL;
    ql_strings_t* s = NULL;
    ql_strings_t* v = NULL;

    //2.connect rlp and become a list
    rlp_nonce->next = rlp_gasPrice;
    rlp_gasPrice->next = rlp_gas;
    rlp_gas->next = rlp_to;
    rlp_to->next = rlp_value;
    rlp_value->next = rlp_payload;
    // ql_strings_t* tmp3 = hexstringToRlpbytes("");
    ql_strings_t* chainid = numericToRlpbytes(1);
    ql_strings_t* zero1 = hexstringToRlpbytes("");
    ql_strings_t* zero2 = hexstringToRlpbytes("");
    ql_strings_t* needhash_bytes;
    
    
    prefix_bytes = connectStringsT(rlp_nonce);//connect to one

    rlp_payload->next = chainid;
    chainid->next = zero1;
    zero1->next = zero2;
    needhash_bytes = connectStringsT(rlp_nonce);
    bytesToRlpbytes(needhash_bytes, RLP_LISTS);
    //needhash_bytes = RLP(nonce, gasprice, gas, to, value, payload, chainid, "", "");
    //""means empty s/v

    FreeStringsT(rlp_nonce);
    if(prefix_bytes == NULL)return NULL;
    if(needhash_bytes == NULL)return NULL;
    //to list

    //3.convert bytes to string (x) there is no need do this
    //get RLP encode, start to sign
    // needhash = BytesToHexstring(needhash_bytes->val, needhash_bytes->len);//needhash

    //4.keccak256
    keccak256(needhash_bytes->val, needhash_bytes->len, &hash_ptr);//get hash
// printf("hash_ptr:%s\n", BytesToHexstring(data_hash, 32));
    FreeStringsT(needhash_bytes);
    //5.sign 
    ql_err_t res;
    if ((res = uEccSign(priv, data_hash, sig)) != ECC_SUCCESS) {//sign
        log_error("rowtx sign failed: %d", res);
        // FreeStringsT(prefix_bytes);
        return NULL;
    }
    // sig[64]+=27;
    ql_uint32_t chainID = 1;
    sig[64] = sig[64] + (chainID * 2 + 35);
    // sig[64] = 28;
    //6.get rsv from sig
    r = CreateStringsT();
    s = CreateStringsT();
    v = CreateStringsT();
    if(v){v->val = StrMalloc(&sig[64],  1);  v->len =  1;}
    if(r){r->val = StrMalloc(&sig[0] , 32);  r->len = 32;}
    if(s){s->val = StrMalloc(&sig[32], 32);  s->len = 32;}
    if(!r || !s || !v || !r->val || !s->val || !v->val) {
        FreeStringsT(r);
        FreeStringsT(s);
        FreeStringsT(v);
        FreeStringsT(prefix_bytes);
    }
    bytesToRlpbytes(r, RLP_BYTES);
    bytesToRlpbytes(s, RLP_BYTES);
    prefix_bytes->next = v;
    v->next = r;
    r->next = s;

    //7.get raw bytes
    //get rsv now, then get raw_bytes = needhash_bytes + rsv
    raw_bytes = connectStringsT(prefix_bytes);
    FreeStringsT(prefix_bytes);
    // printf("len before rlp:%d\n", raw_bytes->len);
    bytesToRlpbytes(raw_bytes, RLP_LISTS);

    //8.get rawtx form raw bytes
    rawtx = BytesToHexstring(raw_bytes->val, raw_bytes->len);
    FreeStringsT(raw_bytes);
    return rawtx;
}

//if invoke contract and doesn't use value, the value should be 0(or error)
char* ethAdapter(ql_int32_t nonce, ql_int64_t gasPrice, ql_int64_t gas, char* to, ql_int64_t value, char* payload, char *priv) {
    char *rpc_content = "{"
            "\"jsonrpc\":\"2.0\","
            "\"method\":\"eth_sendRawTransaction\","
            "\"params\":[\"0x%s\"],"
            "\"id\":1"
            "}";
    ql_size_t maxlen = 0;
    char *rawtx = getRawtx(nonce, gasPrice, gas, to, value, payload, priv);
    char *ret;
    if(rawtx == NULL)return NULL;
    maxlen = strlen(rpc_content) + strlen(rawtx) + 1;
    ret = (char*)calloc(maxlen, sizeof(char));
    if(ret == NULL) {
        free(rawtx);
        return NULL;
    }
    sprintf(ret, rpc_content, rawtx);
    free(rawtx);
    return ret;
}


static void u64ToDecstr(ql_uint64_t in, char* out) {
    ql_size_t i = 0;
    while (in)
    {
        out[i++] = in%10 + '0';
        in /= 10;
    }
    out[i] = 0;
    ql_size_t len = i;
    for(i = 0; i < len/2; i++) {
        char tmp = out[i];
        out[i] = out[len - 1 -i];
        out[len -1 -i] = tmp;
    }
}

char* hpcAdapter(char* method, char* from, char* to, ql_int32_t value, char* payload, ql_uint64_t timestamp, ql_int32_t nonce, char* extra, char* extraId, char* signature){
    // printf("enter hpc\n");
    ql_byte_t rpc_fmt[] = "{"
        "\"jsonrpc\":\"2.0\","
        "\"namespace\":\"global\","
        "\"method\":\"%s\","
        "\"params\":[{"
            "\"from\":\"%s\","
            "%s"//to,deploy don't need to
            "\"value\":%d,"
            "\"payload\":\"0x%s\","
            "\"timestamp\":%s,"
            "\"simulate\":false,"
            "\"nonce\":%d,"
            "\"extra\":\"%s\","
            "%s"//exid,if no exid, there is empty
            "\"signature\":\"%s\""
        "}],"
        "\"id\":\"1\""
        "}";
    char to_s[1024]; 
    sprintf(to_s, "\"to\":\"%s\",", to);
    char exid_s[1024];
    sprintf(exid_s,"\"extraIdstring\":%s,",extraId);
    ql_uint64_t maxlen = strlen(rpc_fmt)  + (method?strlen(method):1)
                    + (from?strnlen(from, 43):1) + strlen(to_s)//(to?strnlen(to, 43):1) 
                    + (payload?strlen(payload):1) + sizeof(timestamp)*8
                    + sizeof(nonce)*8 + (extra?strlen(extra):1) + strlen(exid_s)//(extraId?strlen(extraId):1)
                    + (signature?strlen(signature):1);
    // printf("cal maxlen\n");
    char* ret;
    if(maxlen > 512*1024) {
        log_debug("rpc send too long\n");
        return NULL;
    }
    char buf[maxlen + 1];
    memset(buf, 0, sizeof(buf));

    // printf("exid_s\n");
    
    //time to string
    char time_s[100];
    u64ToDecstr(timestamp, time_s);

    sprintf(buf, rpc_fmt, 
            method?method:"", from?from:"0x",
            (to && /*strlen(to) != 42*/ strcmp(to, ADDR_ZERO)!=0)?to_s:"",
            value, payload?payload:"", time_s, nonce, extra?extra:"", 
            (extraId==NULL||strlen(extraId)==0)?"":exid_s, 
            signature?signature:"");
    
    // printf("sprintf\n");
    ret = (char*)calloc(strlen(buf) + 1, sizeof(char));
    // printf("calloc ret\n");
    if(ret == NULL) {
        printf("alloc memory failed in function hpc_adpter\n");
        return NULL;
    }
    memcpy(ret, buf, strlen(buf));
    return ret; 
}


